using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Oops.Xna.Framework.GameState;

namespace Oops.Xna.Framework
{
	/// <summary>
	/// Defines the base class for any game service-related exceptions.
	/// </summary>
	[Serializable()]
	public abstract class GameServiceException : SystemException
	{
		private Type serviceType;

		/// <summary>
		/// Initializes a new instance of the <see cref="GameServiceException"/> class.
		/// </summary>
		protected GameServiceException() : base() { }

		/// <summary>
		/// Initializes a new instance of the <see cref="GameServiceException"/> class for the specified service type.
		/// </summary>
		/// <param name="serviceType">The type of the service that caused the error.</param>
		protected GameServiceException(Type serviceType)
			: base()
		{
			// Hold onto parameters
			this.serviceType = serviceType;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="GameServiceException"/> class with a 
		/// specified error message for the specified service type.
		/// </summary>
		/// <param name="message">The message that describes the error.</param>
		/// <param name="serviceType">The type of the service that caused the error.</param>
		protected GameServiceException(string message, Type serviceType)
			: base(message)
		{
			// Hold onto parameters
			this.serviceType = serviceType;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="GameServiceException"/> class with a 
		/// specified error message and a reference to the inner exception that is the cause 
		/// of this exception.
		/// </summary>
		/// <param name="message">The message that describes the error.</param>
		/// <param name="innerException">The exception that is the cause of the current exception.</param>
		protected GameServiceException(string message, Exception innerException) : base(message, innerException) { }

		/// <summary>
		/// Initializes a new instance of the <see cref="GameServiceException"/> class with a 
		/// specified error message for the specified service type and a reference to the inner exception 
		/// that is the cause of this exception.
		/// </summary>
		/// <param name="message">The message that describes the error.</param>
		/// <param name="serviceType">The type of the service that caused the error.</param>
		/// <param name="innerException">The exception that is the cause of the current exception.</param>
		protected GameServiceException(string message, Type serviceType, Exception innerException)
			: base(message, innerException)
		{
			// Hold onto parameters
			this.serviceType = serviceType;
		}

		/// <summary>
		/// The type of the service that caused the error.
		/// </summary>
		public Type ServiceType
		{
			get { return this.serviceType; }
		}

		#region Exception Members

		public override string Message
		{
			get
			{
				// Call inherited property.
				string message = base.Message;

				// Append service type exception string.
				if (this.ServiceType != null)
				{
#if XBOX
					message += "\n" + string.Format(this.FormatString, this.ServiceType.FullName);
#else
					message += Environment.NewLine + string.Format(this.FormatString, this.ServiceType.FullName);
#endif
				}

				// Return the message
				return message;
			}
		}

		#endregion

		/// <summary>
		/// Gets the format string for the message.
		/// </summary>
		protected abstract string FormatString { get; }
	}

	/// <summary>
	///The exception that is thrown when a game service is not found.
	/// </summary>
	[Serializable()]
	public class GameServiceNotFoundException : GameServiceException
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="GameServiceNotFoundException"/> class.
		/// </summary>
		/// <param name="serviceType">The type of service not found.</param>
		public GameServiceNotFoundException(Type serviceType) : base(serviceType) { }

		#region GameServiceException Members

		protected override string FormatString
		{
			get { return "Resources.GameServiceNotFound"; }
		}

		#endregion
	}

	/// <summary>
	/// The exception that is thrown when a game service already exists in a <see cref="System.IServiceProvider"/>.
	/// </summary>
	[Serializable()]
	public class GameServiceAlreadyExistsException : GameServiceException
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="GameServiceAlreadyExistsException"/> class.
		/// </summary>
		/// <param name="serviceType">The type of service that already exists.</param>
		public GameServiceAlreadyExistsException(Type serviceType) : base(serviceType) { }

		#region GameServiceException Members

		protected override string FormatString
		{
			get { return "Oops.Xna.Framework.Properties.Resources.GameServiceAlreadyExists"; }
		}

		#endregion
	}

	/// <summary>
	/// An extension to the <see cref="Microsoft.Xna.Framework.Game" /> class.
	/// </summary>
	public static class GameExtension
	{
		/// <summary>
		/// Gets the object providing a specified service.
		/// </summary>
		/// <typeparam name="T">The type of service.</typeparam>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <returns>The object providing the service.</returns>
		public static T GetService<T>(this Game game) where T : class 
		{
			// Call overloaded method.
			return game.GetService<T>(true);
		}

		/// <summary>
		/// Gets the object providing a specified service.
		/// </summary>
		/// <typeparam name="T">The type of service.</typeparam>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <param name="throwError">Determines if an exception should be thrown if the service cannot be found.</param>
		/// <returns>The object providing the service.</returns>
		public static T GetService<T>(this Game game, bool throwError) where T : class
		{
			T result = null;

			// Attempt to get the service from the active game screen first.
			IInternalGameStateService gameStateService = game.Services.GetService(typeof(IInternalGameStateService)) as IInternalGameStateService;
			if (gameStateService != null && gameStateService.ActiveScreen != null)
			{
				result = gameStateService.ActiveScreen.Services.GetService(typeof(T)) as T;
				if (result != null)
				{
					return result;
				}
			}

			// Get the service from the game instance.
			result = game.Services.GetService(typeof(T)) as T;

			// Throw an error if specified.
			if (result == null && throwError)
			{
				throw new GameServiceNotFoundException(typeof(T));
			}

			// Attempt to return the service.
			return result;
		}

		/// <summary>
		/// Adds the specified service to the service container.
		/// </summary>
		/// <typeparam name="T">The type of service to add.</typeparam>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <param name="serviceInstance">An instance of the service type to add. This object must implement or inherit from the type indicated by the serviceType parameter.</param>
		public static void AddService<T>(this Game game, T serviceInstance) where T : class 
		{
			// Call overloaded method.
			game.AddService<T>(serviceInstance, false);
		}

		/// <summary>
		/// Adds the specified service to the service container.
		/// </summary>
		/// <typeparam name="T">The type of service to add.</typeparam>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <param name="serviceInstance">An instance of the service type to add. This object must implement or inherit from the type indicated by the serviceType parameter.</param>
		/// <param name="promote">true if this service should be added to any parent service containers; otherwise, false.</param>
		public static void AddService<T>(this Game game, T serviceInstance, bool promote) where T : class 
		{
			// Make sure the service doesn't already exist.
			if (game.GetService<T>(false) != null)
			{
				throw new GameServiceAlreadyExistsException(typeof(T));
			}

			// Add the service to the game's service collection if specified.
			if (promote)
			{
				game.Services.AddService(typeof(T), serviceInstance);
				return;
			}

			// Attempt to add the service to the active game screen first.
			IInternalGameStateService gameStateService = game.Services.GetService(typeof(IInternalGameStateService)) as IInternalGameStateService;
			if (gameStateService != null && gameStateService.ActiveScreen != null)
			{
				gameStateService.ActiveScreen.Services.AddService(typeof(T), serviceInstance);
				return;
			}

			// Add the service to the game's service collection.
			game.Services.AddService(typeof(T), serviceInstance);
		}

		/// <summary>
		/// Removes the specified service type from the service container.
		/// </summary>
		/// <typeparam name="T">The type of service to remove.</typeparam>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		public static void RemoveService<T>(this Game game) where T : class
		{
			// Call overloaded method.
			game.RemoveService<T>(false);
		}

		/// <summary>
		/// Removes the specified service type from the service container.
		/// </summary>
		/// <typeparam name="T">The type of service to remove.</typeparam>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <param name="promote">true if this service should be removed from any parent service containers; otherwise, false.</param>
		public static void RemoveService<T>(this Game game, bool promote) where T : class
		{
			// Remove the service from the game's service collection if specified.
			if (promote)
			{
				game.Services.RemoveService(typeof(T));
				return;
			}

			// Attempt to remove the service from the active game screen first.
			IInternalGameStateService gameStateService = game.Services.GetService(typeof(IInternalGameStateService)) as IInternalGameStateService;
			if (gameStateService != null && gameStateService.ActiveScreen != null)
			{
				gameStateService.ActiveScreen.Services.RemoveService(typeof(T));
				return;
			}

			// Remove the service from the game's service collection.
			game.Services.RemoveService(typeof(T));
		}

		/// <summary>
		/// Gets the <see cref="Microsoft.Xna.Framework.Pipeline.ContentManager"/> of the <paramref name="game"/>.
		/// </summary>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <returns>Returns the <see cref="Microsoft.Xna.Framework.Pipeline.ContentManager"/>.</returns>
		public static ContentManager GetContent(this Game game)
		{
			// Call overloaded method.
			return game.GetContent(true);
		}

		/// <summary>
		/// Gets the <see cref="Microsoft.Xna.Framework.Pipeline.ContentManager"/> for the current context.
		/// </summary>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <param name="promote"><b>true</b> if <see cref="Microsoft.Xna.Framework.Game.Content"/> should be returned; otherwise, return <see cref="GameScreen.Content"/> from the current screen.</param>
		/// <returns>Returns the <see cref="Microsoft.Xna.Framework.Pipeline.ContentManager"/> based on the value of <paramref name="promote"/>.</returns>
		public static ContentManager GetContent(this Game game, bool promote)
		{
			// Return the game's service provider if specified.
			if (promote)
			{
				return game.Content;
			}

			// Attempt to get the service provider from the active game screen first.
			IInternalGameStateService gameStateService = game.Services.GetService(typeof(IInternalGameStateService)) as IInternalGameStateService;
			if (gameStateService != null && gameStateService.ActiveScreen != null)
			{
				return gameStateService.ActiveScreen.Content;
			}

			// Get the service provider from the game.
			return game.Content;
		}

		/// <summary>
		/// Gets the <see cref="IServiceProvider"/> for the current context.
		/// </summary>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <returns>Returns the <see cref="IServiceProvider"/> in context.</returns>
		public static IServiceProvider GetServiceProvider(this Game game)
		{
			// Call overloaded method.
			return GameExtension.GetServiceProvider(game, false);
		}

		/// <summary>
		/// Gets the <see cref="IServiceProvider"/> for the current context.
		/// </summary>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> reference.</param>
		/// <param name="promote"><b>true</b> if <see cref="Microsoft.Xna.Framework.Game.Services"/> should be returned.  Otherwise, return <see cref="GameScreen.Services"/> from the current screen.</param>
		/// <returns>Returns the <see cref="IServiceProvider"/> based on the value of <paramref name="promote"/>.</returns>
		public static IServiceProvider GetServiceProvider(this Game game, bool promote)
		{
			// Return the game's service provider if specified.
			if (promote)
			{
				return game.Services;
			}

			// Attempt to get the service provider from the active game screen first.
			IInternalGameStateService gameStateService = game.Services.GetService(typeof(IInternalGameStateService)) as IInternalGameStateService;
			if (gameStateService != null && gameStateService.ActiveScreen != null)
			{
				return gameStateService.ActiveScreen.Services;
			}

			// Get the service provider from the game.
			return game.Services;
		}
	}
}
